﻿using UnityEngine;
using System.Collections.Generic;
using System.Collections;
using System;

using BaseLib;

public class AnimationMgr
{
    private const int DEFAULT_TIMER_POOL_SIZE = 10;

    public static Timer REPEAT(int nCount, float fInterval
                                                    , Action<object> pHandler, object pUserData = null
                                                    , float startDelayTime = 0, bool triggerOnStart = false) 
        { return _Instance.Repeat(nCount, fInterval, pHandler, pUserData, startDelayTime, triggerOnStart); }

    public static Timer DELAY(float fTime, Action<object> pHandler, object pUserData = null)
        { return _Instance.Delay(fTime, pHandler, pUserData); }

    public static Timer AddTimer() { return _Instance.AddTickable<Timer>(); }
    public static void RemoveTimer(Timer pTimer) { _Instance.RemoveTickable(pTimer); }

    ///... AnimationMgr inherit Singleton<> will crash, don't know why...
    public static volatile AnimationMgr _Instance = null;
    public static AnimationMgr Instance
    {
        get { if (_Instance == null) { _Instance = new AnimationMgr(); } return _Instance; }
    }

	/// Unmanaged in queue animators
	private QuickList<Tickable> _arrInQueueTickers;

    private int _nManualTickersCount;
	
	/// Timer is a pooled tickers (managed tickers).
	private List<Timer> _arrTimerPool;

    internal float _Time;
    internal float _RealTime;

    public int __AliveTickableCount;

	/// Use this two flag to fix a bug: new tween added during update.
	private bool __bInUpdateing;
	private bool __bNewAddedWhenUpdate;

	public AnimationMgr()
	{
        Debugger.Assert(_Instance == null);

        _Time = 0;
        _RealTime = 0;
        __AliveTickableCount = 0;

        __bInUpdateing = false;
        __bNewAddedWhenUpdate = false;
	}

    /// Add a manual tickable object.
    /// A manual tickable object must be add/remove by AddTickable/RemoveTickable.
    public T AddTickable<T>() where T : Tickable, new()
    {
        T pTickable = new T();

        _nManualTickersCount++;

        return pTickable;
    }

    /// Remove a manual tickable object.
    /// A manual tickable object must be add/remove by AddTickable/RemoveTickable.
    public void RemoveTickable(Tickable pTickable) 
    {
        _nManualTickersCount--;
        Debugger.Assert(_nManualTickersCount >= 0);
        pTickable.Dispose();
    }

	/**
	 * Repeat calling specified function with a constant interval.
	 * 
	 * @param	nCount Repeat count.
	 * @param	nInterval Interval between each call.
	 * @param	pHander The function to be called.
	 * @param	userData Use this param to pass some data to pHander.
	 * 
	 * @return Return the internal timer object if is infinite repeating, or else return null.
	 */
    public Timer Repeat(int nCount, float fInterval
													, Action<object> pHandler
													, object pUserData = null
                                                    , float startDelayTime = 0
                                                    , bool triggerOnStart = false)
	{
        if (nCount == 0) return null;

		Timer timer = _getTimerFromPool();
        timer.IncRef();
		
		timer.SetParams(nCount, fInterval, pHandler, pUserData, startDelayTime, triggerOnStart);
        timer.Start();
		
		return nCount == -1? timer : null;
	}

	/**
	 * Delay some time then doing something
	 * @param	nTime  	time to be delayed.
	 * @param	pHandler		The function to be called when time is up.
	 * @param	pUserData 	Use this param to pass some data to pHander.
	 */
	public Timer Delay(float fTime, Action<object> pHandler, object pUserData = null)
	{
        Timer timer = _getTimerFromPool();
        timer.IncRef();
        timer.Delay(fTime, pHandler, pUserData);
        return timer;
	}

	/// Init TweenMgr
    public void Init(int nTimerPoolSize = DEFAULT_TIMER_POOL_SIZE)
	{
        Debugger.Assert(_arrTimerPool == null, "Should never call AnimationMgr.Init more than once!");

		// Init timer pool
        _arrTimerPool = new List<Timer>();
        for (int i = 0; i < nTimerPoolSize; i++)
		{
			Timer pTimer= new Timer();
            pTimer._Type |= TickableType.Pooled;
			_arrTimerPool.Add(pTimer);
		}		
		
		_arrInQueueTickers = new QuickList<Tickable>();
	}
    
    public void Uninit()
    {
        _arrTimerPool.Clear();
        _arrInQueueTickers.Clear();
    }

	public void _Update(float fTime, float fRealTime)
	{
		__bInUpdateing = true;
        __bNewAddedWhenUpdate = false;

        _Time = fTime;
        _RealTime = fRealTime;

		__AliveTickableCount = 0;

        _updateTickables(fTime, fRealTime);
		
		__bInUpdateing = false;
	}

    /// Internal publics
	public void _PlayTickable(Tickable tickable)
	{
		// push tween into list. 
		if (tickable._QueueIndex == -1)
		{
            if (__bInUpdateing) __bNewAddedWhenUpdate = true; 
			
			tickable._QueueIndex = this._arrInQueueTickers.Count;
			this._arrInQueueTickers.Add(tickable);
		}
	}	

    ///------------ Implementation ------------------
    private void _updateTickables(float fTime, float fRealTime)
	{
		int iLastDeadIndex = -1;
        int nCount = _arrInQueueTickers.Count;

        Tickable[] arrTickers = _arrInQueueTickers._Buffer;

		for (int i=0; i<nCount; ++i)
		{
            Tickable pTicker = arrTickers[i];

			if (pTicker._IsDead) 
			{
				iLastDeadIndex = i;
				continue;
			}
			
			if (pTicker.status == TickerStatus.PUASED)  continue;

            pTicker._Tick( pTicker._Scalable ? fTime : fRealTime);
			
			__AliveTickableCount ++;		
		}
		
		/// Note: if __bNewAddedWhenUpdate == true, means some new tiker be added 
		/// during updating, so the iLastDeadIndex is invalid!
		if (iLastDeadIndex != -1 && !__bNewAddedWhenUpdate)
		{
			Tickable lastDeadTicker = _arrInQueueTickers[iLastDeadIndex];	
			lastDeadTicker._QueueIndex = -1;

            lastDeadTicker.DecRef();
						
			if (iLastDeadIndex != nCount - 1)
			{
				_arrInQueueTickers[iLastDeadIndex] = _arrInQueueTickers[nCount - 1];
			}

            _arrInQueueTickers.RemoveAt(nCount - 1);
		}
	}	

	private Timer _getTimerFromPool()
	{
		Timer timer = null;
        int nCount = _arrTimerPool.Count;
		if (nCount > 0)
		{
            timer = _arrTimerPool[nCount - 1];
            _arrTimerPool.RemoveAt(nCount - 1);
		}
		else
		{
			timer = new Timer();
            timer._MarkType(TickableType.Pooled);
		}
		
		return timer;
	}

    internal void _FreeTimer(Timer timer)
    {
        _arrTimerPool.Add(timer);
    }

}



